#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <pthread.h>

void *brightness_thread(void *args)
{
    int fd = *((int *)args);
    int brightness_min = 0;
    int brightness_max = 0;
    struct v4l2_queryctrl qctrl;
    struct v4l2_control ctrl;
    
    // 获取V4L2_CID_BRIGHTNESS参数
    memset(&qctrl, 0, sizeof(qctrl));
    qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
    if (0 != ioctl(fd, VIDIOC_QUERYCTRL, &qctrl))
    {
        printf("can not query brightness\n");
        return NULL;
    }

    // 获取brightness的最大值和最小值
    brightness_max = qctrl.maximum;
    brightness_min = qctrl.minimum;

    // 获取当前的参数
    ctrl.id = V4L2_CID_BRIGHTNESS;
    ioctl(fd, VIDIOC_G_CTRL, &ctrl);

    while (1) {
        brightness_min++;
        if (brightness_min > brightness_max) {
            brightness_min = qctrl.minimum;
        }

        // 设置参数
        memset(&ctrl, 0, sizeof(struct v4l2_control));
        ctrl.id = V4L2_CID_BRIGHTNESS;
        ctrl.value = brightness_min;
        ioctl(fd, VIDIOC_S_CTRL, &ctrl);
    }

    return NULL;
}

int main(int argc, char const *argv[])
{
    int fd = 0;
    struct v4l2_fmtdesc fmtdesc;     // 用于描述视频格式的结构体
    struct v4l2_frmsizeenum fsenum;  // 用于枚举视频帧大小的结构体
    struct v4l2_format fmt;          // 用于存储视频设备的格式信息
    struct v4l2_capability cap;     // 用于存储视频设备的捕获参数信息
    struct v4l2_requestbuffers rb;
    struct v4l2_buffer buf;
    int fsenum_index = 0;
    int fmt_index = 0;
    int buf_cnt = 0;
    void *bufs[32] = {0};
    struct epoll_event ev, events[10];
    int nfds = 0, epollfd = 0, n = 0;
    char filename[32] = {0};
    int file_cnt = 0;
    int fd_file = 0;
    pthread_t thread = 0;

    /* 打开视频设备 */
    fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open %s failed, errno: %d\n", argv[1], errno);
        return -1;
    }

    /* 查询摄像头的功能 */
    memset(&cap, 0, sizeof(struct v4l2_capability));

    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
        printf("query cap failed, errno: %d\n", errno);
        return -1;
    } else {
        // 检测摄像头支持的功能
        if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
            printf("device does not support video capture\n");
            return -1;
        }
        
        if ((cap.capabilities & V4L2_CAP_STREAMING) == 0) {
            printf("device does not support streaming i/o\n");
            return -1;
        }
    }

    while (1) {
        /* 枚举视频格式 */
        fmtdesc.index = fmt_index;
        fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) < 0) {
            break;
        }

        /* 枚举视频帧大小 */
        for (fsenum_index = 0; ; fsenum_index++) {

            memset(&fsenum, 0, sizeof(fsenum));
            fsenum.index = fsenum_index;
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
           
            if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) < 0) {
                break;
            } else {
                printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat,\
                    fsenum_index, fsenum.discrete.width, fsenum.discrete.height);
            }
        }
        fmt_index++;
    }

    /* 设置视频格式 */
    memset(&fmt, 0, sizeof(struct v4l2_format));
    // 设置视频捕获类型为V4L2_BUF_TYPE_VIDEO_CAPTURE
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    // 设置像素宽度为1024
    fmt.fmt.pix.width = 1024;
    // 设置像素高度为768
    fmt.fmt.pix.height = 768;
    // 设置像素格式为V4L2_PIX_FMT_MJPEG
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    // 设置场模式为任意模式
    fmt.fmt.pix.field = V4L2_FIELD_ANY; 
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        printf("set format failed, errno: %d\n", errno);
        return -1;
    } else {
        printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
    }

    /* 申请map的buffer */
    memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
    rb.count = 32;
    rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    rb.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_REQBUFS, &rb) < 0) {
        printf("request buffers failed, errno: %d\n", errno);
        return -1;
    } else {
        /* 申请buffer成功后, map进行映射 */
        buf_cnt = rb.count;
        for (int i = 0; i < buf_cnt; i++) {
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index = i;
            // 把buffer放入链表队列
            if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
                printf("query buffer failed, errno: %d\n", errno);
                return -1;
            } else {
                /* mmap */
                bufs[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
                if (bufs[i] == MAP_FAILED) {
                    printf("mmap failed, errno: %d\n", errno);
                    return -1;
                }
            }
        }
    }

    printf("map %d buffers ok\n", buf_cnt);

    /* 把所有的buufer放入空闲列表 */
    for (int i = 0; i < buf_cnt; i++) {
        memset(&buf, 0, sizeof(struct v4l2_buffer));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            printf("queue buffer failed, errno: %d\n", errno);
            // 释放已经申请的buffer
            return -1;
        }
    }

    printf("queue buffers ok\n");

    /* 启动摄像头 */
    if (ioctl(fd, VIDIOC_STREAMON, &rb.type) < 0) {
        printf("streamon failed, errno: %d\n", errno);
        return -1;
    }

    printf("start capture ok\n");

    epollfd = epoll_create1(0);
    if (epollfd == -1) {
        perror("epoll_create1");
        return -1;
    }

    ev.events = EPOLLIN;
    ev.data.fd = fd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        perror("epoll_ctl: listen_sock");
        return -1;
    }

    /* 创建线程控制摄像头的亮度 */
    pthread_create(&thread, NULL, brightness_thread, (void *)&fd);
    pthread_detach(thread);
    
    /* 通过epoll保存图片 */
    while (1) {
        nfds = epoll_wait(epollfd, events, 10, -1); // 500ms, -1表示阻塞
        if (nfds == -1) {
            /* 小于0时,可以获取 */
            perror("epoll_wait");
            return -1;
        } else if (nfds == 0) {
            /* 驱动中返回0 */
            printf("timeout\n");
        } else {
            for (n = 0; n < nfds; ++n) {
                if (events[n].data.fd == fd) {
                    if (events[n].events & EPOLLIN) {
                        /* 从QUEUE中取出一个buffer */
                        memset(&buf, 0, sizeof(struct v4l2_buffer));
                        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                        buf.memory = V4L2_MEMORY_MMAP;
                        if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
                            printf("dequeue buffer failed, errno: %d\n", errno);
                        } else {
                            // 把buffer存为jpg文件
                            sprintf(filename, "frame-%d.jpg", file_cnt++);
                            fd_file = open(filename, O_CREAT | O_WRONLY, 0666);
                            if (fd_file < 0) {
                                printf("open %s file failed, errno: %d\n", filename, errno);
                            }
                            printf("save %s\n", filename);
                            // 把buffer数据写入file
                            if (write(fd_file, bufs[buf.index], buf.bytesused) < 0) {
                                printf("write %s file failed, errno: %d\n", filename, errno);
                            }
                            close(fd_file);
                            /* 把buffer放入QUEUE中 */
                            if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
                                printf("queue buffer failed, errno: %d\n", errno);
                            }
                        }
                    }
                }
            }
        }
    }

    // 关闭摄像头
    if (ioctl(fd, VIDIOC_STREAMOFF, &rb.type) < 0) {
        printf("streamoff failed, errno: %d\n", errno);
        return -1;
    }

    close(fd);

    /* code */
    return 0;
}
